package com.lipaluma.excel.parser;

import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.util.Collection;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import com.lipaluma.context.Context;
import com.lipaluma.context.mapping.impl.ColumnMappingManager;
import com.lipaluma.excel.annotations.config.Excel;
import com.lipaluma.excel.config.ExcelConfiguration;
import com.lipaluma.excel.xml.XmlExcelMappings;
import com.lipaluma.excel.xml.configuration.XmlExcelConfiguration;
import com.lipaluma.exceptions.CsvParserException;
import com.lipaluma.exceptions.CsvParserProcessException;
import com.lipaluma.field.mapper.FieldMapper;
import com.lipaluma.field.mapper.mapping.Column;
import com.lipaluma.field.mapper.mapping.MappingInfo;
import com.lipaluma.field.validator.strategies.ValidationStrategyEnum;
import com.lipaluma.io.Row;
import com.lipaluma.io.SourceReader;
import com.lipaluma.io.SourceWriter;
import com.lipaluma.parser.AbstractAllDataParser;
import com.lipaluma.util.FileUtils;
import com.lipaluma.xml.XmlMappings;
import com.lipaluma.xml.configuration.XmlConfiguration;

public class ExcelParser<TARGET> extends AbstractAllDataParser<TARGET, ExcelConfiguration> {
	private ExcelParser(Class<TARGET> clazz) {
		super(clazz);
	}
	private ExcelParser(Class<TARGET> clazz, XmlMappings xmlMappings) {
		super(clazz, xmlMappings);
	}
	private ExcelParser(Builder<TARGET> builder) {
		super(builder);
	}
	
	@Override
	protected Context createContext() {
		return new Context(new ColumnMappingManager());
	}
	
	protected void processHeader(SourceReader fileReader) {
		if(configuration.hasHeader()) {
			for(int i=1; i<=configuration.getLinesHeaderNumber(); i++) {
				Row row = fileReader.next();
				if(configuration.getHeaderLine() == i && configuration.isColumnsMappedByHeaders()){
					mapHeaders(row.toArray());
				}
			}
		} else if(configuration.isColumnsMappedByHeaders())
			throw new CsvParserProcessException("Error in CSV configuration : mapping by header's name is mentioned without headers");
	}

	public void mapHeaders(String[] headers) {
		for(FieldMapper mapper : fieldMappers) {
			for (MappingInfo mapping : mapper.getMappings()) {
				if(Column.class.isAssignableFrom(mapping.getClass())){
					for (int index = 0; index < headers.length; index++) {
						if(((Column)mapping).mapHeader(headers[index], index))
							break;
					}
				}
			}
		}
	}

	protected void processHeader(SourceWriter fileWriter) {
		if(configuration.writeHeader()) {
			Row row = fileWriter.createNewRow();
			for(FieldMapper mapper : fieldMappers) {
				MappingInfo[] mappings = mapper.getMappings();
				for (int i = 0; i < mappings.length; i++) {
					Column column = (Column)mappings[i];
					column.writeHeader(row);
				}
			}
		}
	}
	
	public void writeHeaders(String[] headers) {
		for (int index = 0; index < headers.length; index++) {
			for(FieldMapper fieldMapper : fieldMappers) {
				Column[] columns = (Column[])fieldMapper.getMappings();
				for (Column column : columns) {
					if(column.getColumnIndex() == index) {
						column.setHeaderName(headers[index]);
					}
				}
			}			
		}
	}
	
	@Override
	protected ExcelConfiguration createConfiguration(Class<TARGET> clazz) {
		if(clazz.isAnnotationPresent(Excel.class))
			return new ExcelConfiguration(clazz.getAnnotation(Excel.class));
		else
			throw new CsvParserException("Error on class "+clazz+" : The annotation Excel is not present");
	}

	@Override
	protected ExcelConfiguration createConfiguration(Class<TARGET> clazz, XmlConfiguration configuration) {
		XmlExcelConfiguration excelConfiguration = (XmlExcelConfiguration) configuration;
		return new ExcelConfiguration(excelConfiguration);
	}

	public static <T> ExcelParser<T> forClass(Class<T> clazz) {
		return new ExcelParser<T>(clazz);
	}

	public static <T> ExcelParser<T> forClass(Class<T> clazz, ValidationStrategyEnum validationStrategy) {
		ExcelParser<T> parser = forClass(clazz);
		parser.setValidationStrategy(validationStrategy);
		return parser;
	}

	public static <TARGET> ExcelParser<TARGET> byXml(Class<TARGET> clazz, InputStream xml) {
		try {
			JAXBContext context = JAXBContext.newInstance(XmlExcelMappings.class);
			Unmarshaller unmarshaller = context.createUnmarshaller();
			XmlMappings xmlMappings = (XmlMappings)unmarshaller.unmarshal(xml);
			return new ExcelParser<TARGET>(clazz, xmlMappings);
		} catch (JAXBException e) {
			throw new CsvParserException("Error on parsing the xml file of mappings", e);
		}
	}

	/**
	 * Construct a Mapper for the entity target class
	 * 
	 * @param file path of the xml file configuration
	 * @param clazz target class
	 * @return row mapper
	 */
	public static <TARGET> ExcelParser<TARGET> byXml(Class<TARGET> clazz, String xmlFile) throws FileNotFoundException {
		return byXml(clazz, FileUtils.getInputStreamFromFilePath(xmlFile));
	}

	public List<TARGET> unmarshal(InputStream in) {
		SourceReader fileReader = sourceFactory.createSourceReader(in);
		return unmarshalAll(fileReader);
	}

	public List<TARGET> unmarshal(Reader in) {
		SourceReader fileReader = sourceFactory.createSourceReader(in);
		return unmarshalAll(fileReader);
	}

	@Override
	protected List<TARGET> unmarshalAll(SourceReader fileReader) {
		processHeader(fileReader);
		return super.unmarshalAll(fileReader);
	}
	
	public void processAllObjects(Collection<TARGET> objects, OutputStream out) {
		SourceWriter fw = sourceFactory.createSourceWriter(out);
		marshalAll(objects, fw);
	}
	
	public void processAllObjects(Collection<TARGET> objects, Writer writer) {
		SourceWriter fw = sourceFactory.createSourceWriter(writer);
		marshalAll(objects, fw);
	}

	@Override
	protected void marshalAll(Collection<TARGET> objects, SourceWriter fw) {
		processHeader(fw);
		super.marshalAll(objects, fw);
	}
	
	public static <TARGET> Builder<TARGET> builder(Class<TARGET> targetClass) {
		return new Builder<TARGET>(targetClass);
	}
	
	public static class Builder<TARGET> extends AbstractAllDataParser.Builder<ExcelParser<TARGET>, TARGET, ExcelConfiguration> {
		public Builder(Class<TARGET> clazz) {
			super(clazz, new Context(new ColumnMappingManager()));
		}

		public ExcelParser<TARGET> build() {
			if(configuration == null)
				new CsvParserException("Error on creating excel parser : no configuration defined");
			return new ExcelParser<TARGET>(this);
		}
	}
}
